/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2008 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package javax.mail.internet;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Vector;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.BodyPart;
import javax.mail.FolderClosedException;
import javax.mail.IllegalWriteException;
import javax.mail.Message;
import javax.mail.MessageRemovedException;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;

import com.sun.mail.imap.IMAPInputStream;
import com.sun.mail.util.ASCIIUtility;
import com.sun.mail.util.FolderClosedIOException;
import com.sun.mail.util.LineOutputStream;
import com.sun.mail.util.MessageRemovedIOException;
import com.sun.mail.util.PropUtil;

/**
 * This class represents a MIME body part. It implements the
 * <code>BodyPart</code> abstract class and the <code>MimePart</code> interface.
 * MimeBodyParts are contained in <code>MimeMultipart</code> objects.
 * <p>
 * MimeBodyPart uses the <code>InternetHeaders</code> class to parse and store
 * the headers of that body part.
 * <p>
 * <hr>
 * <strong>A note on RFC 822 and MIME headers</strong>
 * <p>
 * RFC 822 header fields <strong>must</strong> contain only US-ASCII characters.
 * MIME allows non ASCII characters to be present in certain portions of certain
 * headers, by encoding those characters. RFC 2047 specifies the rules for doing
 * this. The MimeUtility class provided in this package can be used to to
 * achieve this. Callers of the <code>setHeader</code>, <code>addHeader</code>,
 * and <code>addHeaderLine</code> methods are responsible for enforcing the MIME
 * requirements for the specified headers. In addition, these header fields must
 * be folded (wrapped) before being sent if they exceed the line length
 * limitation for the transport (1000 bytes for SMTP). Received headers may have
 * been folded. The application is responsible for folding and unfolding headers
 * as appropriate.
 * <p>
 * 
 * @author John Mani
 * @author Bill Shannon
 * @author Kanwar Oberoi
 * @see javax.mail.Part
 * @see javax.mail.internet.MimePart
 * @see javax.mail.internet.MimeUtility
 */

public class MimeBodyPart extends BodyPart implements MimePart
{

	// Paranoia:
	// allow this last minute change to be disabled if it causes problems
	private static boolean		setDefaultTextCharset	= PropUtil
																.getBooleanSystemProperty(
																		"mail.mime.setdefaulttextcharset",
																		true);

	private static boolean		setContentTypeFileName	= PropUtil
																.getBooleanSystemProperty(
																		"mail.mime.setcontenttypefilename",
																		true);

	private static boolean		encodeFileName			= PropUtil
																.getBooleanSystemProperty(
																		"mail.mime.encodefilename",
																		false);
	private static boolean		decodeFileName			= PropUtil
																.getBooleanSystemProperty(
																		"mail.mime.decodefilename",
																		false);

	// Paranoia:
	// allow this last minute change to be disabled if it causes problems
	static boolean				cacheMultipart			= // accessed by
														// MimeMessage
														PropUtil
																.getBooleanSystemProperty(
																		"mail.mime.cachemultipart",
																		true);

	/**
	 * The DataHandler object representing this Part's content.
	 */
	protected DataHandler		dh;

	/**
	 * Byte array that holds the bytes of the content of this Part.
	 */
	protected byte[]			content;

	/**
	 * If the data for this body part was supplied by an InputStream that
	 * implements the SharedInputStream interface, <code>contentStream</code> is
	 * another such stream representing the content of this body part. In this
	 * case, <code>content</code> will be null.
	 * 
	 * @since JavaMail 1.2
	 */
	protected InputStream		contentStream;

	/**
	 * The InternetHeaders object that stores all the headers of this body part.
	 */
	protected InternetHeaders	headers;

	/**
	 * If our content is a Multipart of Message object, we save it the first
	 * time it's created by parsing a stream so that changes to the contained
	 * objects will not be lost.
	 */
	private Object				cachedContent;

	/**
	 * An empty MimeBodyPart object is created. This body part maybe filled in
	 * by a client constructing a multipart message.
	 */
	public MimeBodyPart()
	{
		super();
		headers = new InternetHeaders();
	}

	/**
	 * Constructs a MimeBodyPart by reading and parsing the data from the
	 * specified input stream. The parser consumes data till the end of the
	 * given input stream. The input stream must start at the beginning of a
	 * valid MIME body part and must terminate at the end of that body part.
	 * <p>
	 * Note that the "boundary" string that delimits body parts must
	 * <strong>not</strong> be included in the input stream. The intention is
	 * that the MimeMultipart parser will extract each body part's bytes from a
	 * multipart stream and feed them into this constructor, without the
	 * delimiter strings.
	 * 
	 * @param is
	 *            the body part Input Stream
	 */
	public MimeBodyPart(InputStream is) throws MessagingException
	{
		if (!(is instanceof ByteArrayInputStream)
				&& !(is instanceof BufferedInputStream)
				&& !(is instanceof SharedInputStream)) is = new BufferedInputStream(
				is);

		headers = new InternetHeaders(is);

		if (is instanceof SharedInputStream)
		{
			SharedInputStream sis = (SharedInputStream) is;
			contentStream = sis.newStream(sis.getPosition(), -1);
		}
		else
		{
			try
			{
				content = ASCIIUtility.getBytes(is);
			}
			catch (IOException ioex)
			{
				throw new MessagingException("Error reading input stream", ioex);
			}
		}

	}

	/**
	 * Constructs a MimeBodyPart using the given header and content bytes.
	 * <p>
	 * Used by providers.
	 * 
	 * @param headers
	 *            The header of this part
	 * @param content
	 *            bytes representing the body of this part.
	 */
	public MimeBodyPart(InternetHeaders headers, byte[] content)
			throws MessagingException
	{
		super();
		this.headers = headers;
		this.content = content;
	}

	/**
	 * Return the size of the content of this body part in bytes. Return -1 if
	 * the size cannot be determined.
	 * <p>
	 * Note that this number may not be an exact measure of the content size and
	 * may or may not account for any transfer encoding of the content.
	 * <p>
	 * This implementation returns the size of the <code>content</code> array
	 * (if not null), or, if <code>contentStream</code> is not null, and the
	 * <code>available</code> method returns a positive number, it returns that
	 * number as the size. Otherwise, it returns -1.
	 * 
	 * @return size in bytes, or -1 if not known
	 */
	public int getSize() throws MessagingException
	{
		if (content != null) return content.length;
		if (contentStream != null)
		{
			try
			{
				int size = contentStream.available();
				// only believe the size if it's greate than zero, since zero
				// is the default returned by the InputStream class itself
				if (size > 0) return size;
			}
			catch (IOException ex)
			{
				// ignore it
			}
		}
		return -1;
	}

	/**
	 * Return the number of lines for the content of this Part. Return -1 if
	 * this number cannot be determined.
	 * <p>
	 * Note that this number may not be an exact measure of the content length
	 * and may or may not account for any transfer encoding of the content.
	 * <p>
	 * This implementation returns -1.
	 * 
	 * @return number of lines, or -1 if not known
	 */
	public int getLineCount() throws MessagingException
	{
		return -1;
	}

	/**
	 * Returns the value of the RFC 822 "Content-Type" header field. This
	 * represents the content type of the content of this body part. This value
	 * must not be null. If this field is unavailable, "text/plain" should be
	 * returned.
	 * <p>
	 * This implementation uses <code>getHeader(name)</code> to obtain the
	 * requisite header field.
	 * 
	 * @return Content-Type of this body part
	 */
	public String getContentType() throws MessagingException
	{
		String s = getHeader("Content-Type", null);
		if (s == null) s = "text/plain";

		return s;
	}

	/**
	 * Is this Part of the specified MIME type? This method compares
	 * <strong>only the <code>primaryType</code> and <code>subType</code>
	 * </strong>. The parameters of the content types are ignored.
	 * <p>
	 * For example, this method will return <code>true</code> when comparing a
	 * Part of content type <strong>"text/plain"</strong> with
	 * <strong>"text/plain; charset=foobar"</strong>.
	 * <p>
	 * If the <code>subType</code> of <code>mimeType</code> is the special
	 * character '*', then the subtype is ignored during the comparison.
	 */
	public boolean isMimeType(String mimeType) throws MessagingException
	{
		return isMimeType(this, mimeType);
	}

	/**
	 * Returns the value of the "Content-Disposition" header field. This
	 * represents the disposition of this part. The disposition describes how
	 * the part should be presented to the user.
	 * <p>
	 * If the Content-Disposition field is unavailable, null is returned.
	 * <p>
	 * This implementation uses <code>getHeader(name)</code> to obtain the
	 * requisite header field.
	 * 
	 * @see #headers
	 */
	public String getDisposition() throws MessagingException
	{
		return getDisposition(this);
	}

	/**
	 * Set the "Content-Disposition" header field of this body part. If the
	 * disposition is null, any existing "Content-Disposition" header field is
	 * removed.
	 * 
	 * @exception IllegalWriteException
	 *                if the underlying implementation does not support
	 *                modification
	 * @exception IllegalStateException
	 *                if this body part is obtained from a READ_ONLY folder.
	 */
	public void setDisposition(String disposition) throws MessagingException
	{
		setDisposition(this, disposition);
	}

	/**
	 * Returns the content transfer encoding from the
	 * "Content-Transfer-Encoding" header field. Returns <code>null</code> if
	 * the header is unavailable or its value is absent.
	 * <p>
	 * This implementation uses <code>getHeader(name)</code> to obtain the
	 * requisite header field.
	 * 
	 * @see #headers
	 */
	public String getEncoding() throws MessagingException
	{
		return getEncoding(this);
	}

	/**
	 * Returns the value of the "Content-ID" header field. Returns
	 * <code>null</code> if the field is unavailable or its value is absent.
	 * <p>
	 * This implementation uses <code>getHeader(name)</code> to obtain the
	 * requisite header field.
	 */
	public String getContentID() throws MessagingException
	{
		return getHeader("Content-Id", null);
	}

	/**
	 * Set the "Content-ID" header field of this body part. If the
	 * <code>cid</code> parameter is null, any existing "Content-ID" is removed.
	 * 
	 * @exception IllegalWriteException
	 *                if the underlying implementation does not support
	 *                modification
	 * @exception IllegalStateException
	 *                if this body part is obtained from a READ_ONLY folder.
	 * @exception MessagingException
	 * @since JavaMail 1.3
	 */
	public void setContentID(String cid) throws MessagingException
	{
		if (cid == null) removeHeader("Content-ID");
		else setHeader("Content-ID", cid);
	}

	/**
	 * Return the value of the "Content-MD5" header field. Returns
	 * <code>null</code> if this field is unavailable or its value is absent.
	 * <p>
	 * This implementation uses <code>getHeader(name)</code> to obtain the
	 * requisite header field.
	 */
	public String getContentMD5() throws MessagingException
	{
		return getHeader("Content-MD5", null);
	}

	/**
	 * Set the "Content-MD5" header field of this body part.
	 * 
	 * @exception IllegalWriteException
	 *                if the underlying implementation does not support
	 *                modification
	 * @exception IllegalStateException
	 *                if this body part is obtained from a READ_ONLY folder.
	 */
	public void setContentMD5(String md5) throws MessagingException
	{
		setHeader("Content-MD5", md5);
	}

	/**
	 * Get the languages specified in the Content-Language header of this
	 * MimePart. The Content-Language header is defined by RFC 1766. Returns
	 * <code>null</code> if this header is not available or its value is absent.
	 * <p>
	 * This implementation uses <code>getHeader(name)</code> to obtain the
	 * requisite header field.
	 */
	public String[] getContentLanguage() throws MessagingException
	{
		return getContentLanguage(this);
	}

	/**
	 * Set the Content-Language header of this MimePart. The Content-Language
	 * header is defined by RFC 1766.
	 * 
	 * @param languages
	 *            array of language tags
	 */
	public void setContentLanguage(String[] languages)
			throws MessagingException
	{
		setContentLanguage(this, languages);
	}

	/**
	 * Returns the "Content-Description" header field of this body part. This
	 * typically associates some descriptive information with this part. Returns
	 * null if this field is unavailable or its value is absent.
	 * <p>
	 * If the Content-Description field is encoded as per RFC 2047, it is
	 * decoded and converted into Unicode. If the decoding or conversion fails,
	 * the raw data is returned as is.
	 * <p>
	 * This implementation uses <code>getHeader(name)</code> to obtain the
	 * requisite header field.
	 * 
	 * @return content description
	 */
	public String getDescription() throws MessagingException
	{
		return getDescription(this);
	}

	/**
	 * Set the "Content-Description" header field for this body part. If the
	 * description parameter is <code>null</code>, then any existing
	 * "Content-Description" fields are removed.
	 * <p>
	 * If the description contains non US-ASCII characters, it will be encoded
	 * using the platform's default charset. If the description contains only
	 * US-ASCII characters, no encoding is done and it is used as is.
	 * <p>
	 * Note that if the charset encoding process fails, a MessagingException is
	 * thrown, and an UnsupportedEncodingException is included in the chain of
	 * nested exceptions within the MessagingException.
	 * 
	 * @param description
	 *            content description
	 * @exception IllegalWriteException
	 *                if the underlying implementation does not support
	 *                modification
	 * @exception IllegalStateException
	 *                if this body part is obtained from a READ_ONLY folder.
	 * @exception MessagingException
	 *                otherwise; an UnsupportedEncodingException may be included
	 *                in the exception chain if the charset conversion fails.
	 */
	public void setDescription(String description) throws MessagingException
	{
		setDescription(description, null);
	}

	/**
	 * Set the "Content-Description" header field for this body part. If the
	 * description parameter is <code>null</code>, then any existing
	 * "Content-Description" fields are removed.
	 * <p>
	 * If the description contains non US-ASCII characters, it will be encoded
	 * using the specified charset. If the description contains only US-ASCII
	 * characters, no encoding is done and it is used as is.
	 * <p>
	 * Note that if the charset encoding process fails, a MessagingException is
	 * thrown, and an UnsupportedEncodingException is included in the chain of
	 * nested exceptions within the MessagingException.
	 * 
	 * @param description
	 *            Description
	 * @param charset
	 *            Charset for encoding
	 * @exception IllegalWriteException
	 *                if the underlying implementation does not support
	 *                modification
	 * @exception IllegalStateException
	 *                if this body part is obtained from a READ_ONLY folder.
	 * @exception MessagingException
	 *                otherwise; an UnsupportedEncodingException may be included
	 *                in the exception chain if the charset conversion fails.
	 */
	public void setDescription(String description, String charset)
			throws MessagingException
	{
		setDescription(this, description, charset);
	}

	/**
	 * Get the filename associated with this body part.
	 * <p>
	 * Returns the value of the "filename" parameter from the
	 * "Content-Disposition" header field of this body part. If its not
	 * available, returns the value of the "name" parameter from the
	 * "Content-Type" header field of this body part. Returns <code>null</code>
	 * if both are absent.
	 * <p>
	 * If the <code>mail.mime.encodefilename</code> System property is set to
	 * true, the {@link MimeUtility#decodeText MimeUtility.decodeText} method
	 * will be used to decode the filename. While such encoding is not supported
	 * by the MIME spec, many mailers use this technique to support non-ASCII
	 * characters in filenames. The default value of this property is false.
	 * 
	 * @return filename
	 */
	public String getFileName() throws MessagingException
	{
		return getFileName(this);
	}

	/**
	 * Set the filename associated with this body part, if possible.
	 * <p>
	 * Sets the "filename" parameter of the "Content-Disposition" header field
	 * of this body part. For compatibility with older mailers, the "name"
	 * parameter of the "Content-Type" header is also set.
	 * <p>
	 * If the <code>mail.mime.encodefilename</code> System property is set to
	 * true, the {@link MimeUtility#encodeText MimeUtility.encodeText} method
	 * will be used to encode the filename. While such encoding is not supported
	 * by the MIME spec, many mailers use this technique to support non-ASCII
	 * characters in filenames. The default value of this property is false.
	 * 
	 * @exception IllegalWriteException
	 *                if the underlying implementation does not support
	 *                modification
	 * @exception IllegalStateException
	 *                if this body part is obtained from a READ_ONLY folder.
	 */
	public void setFileName(String filename) throws MessagingException
	{
		setFileName(this, filename);
	}

	/**
	 * Return a decoded input stream for this body part's "content".
	 * <p>
	 * This implementation obtains the input stream from the DataHandler. That
	 * is, it invokes getDataHandler().getInputStream();
	 * 
	 * @return an InputStream
	 * @exception MessagingException
	 * @exception IOException
	 *                this is typically thrown by the DataHandler. Refer to the
	 *                documentation for javax.activation.DataHandler for more
	 *                details.
	 * @see #getContentStream
	 * @see javax.activation.DataHandler#getInputStream
	 */
	public InputStream getInputStream() throws IOException, MessagingException
	{
		return getDataHandler().getInputStream();
	}

	/**
	 * Produce the raw bytes of the content. This method is used when creating a
	 * DataHandler object for the content. Subclasses that can provide a
	 * separate input stream for just the Part content might want to override
	 * this method.
	 * <p>
	 * 
	 * @see #content
	 * @see MimeMessage#getContentStream
	 */
	protected InputStream getContentStream() throws MessagingException
	{
		if (contentStream != null) return ((SharedInputStream) contentStream)
				.newStream(0, -1);
		if (content != null) return new ByteArrayInputStream(content);

		throw new MessagingException("No content");
	}

	/**
	 * Return an InputStream to the raw data with any Content-Transfer-Encoding
	 * intact. This method is useful if the "Content-Transfer-Encoding" header
	 * is incorrect or corrupt, which would prevent the
	 * <code>getInputStream</code> method or <code>getContent</code> method from
	 * returning the correct data. In such a case the application may use this
	 * method and attempt to decode the raw data itself.
	 * <p>
	 * This implementation simply calls the <code>getContentStream</code>
	 * method.
	 * 
	 * @see #getInputStream
	 * @see #getContentStream
	 * @since JavaMail 1.2
	 */
	public InputStream getRawInputStream() throws MessagingException
	{
		return getContentStream();
	}

	/**
	 * Return a DataHandler for this body part's content.
	 * <p>
	 * The implementation provided here works just like the the implementation
	 * in MimeMessage.
	 * 
	 * @see MimeMessage#getDataHandler
	 */
	public DataHandler getDataHandler() throws MessagingException
	{
		if (dh == null) dh = new DataHandler(new MimePartDataSource(this));
		return dh;
	}

	/**
	 * Return the content as a Java object. The type of the object returned is
	 * of course dependent on the content itself. For example, the native format
	 * of a text/plain content is usually a String object. The native format for
	 * a "multipart" content is always a Multipart subclass. For content types
	 * that are unknown to the DataHandler system, an input stream is returned
	 * as the content.
	 * <p>
	 * This implementation obtains the content from the DataHandler. That is, it
	 * invokes getDataHandler().getContent(); If the content is a Multipart or
	 * Message object and was created by parsing a stream, the object is cached
	 * and returned in subsequent calls so that modifications to the content
	 * will not be lost.
	 * 
	 * @return Object
	 * @exception MessagingException
	 * @exception IOException
	 *                this is typically thrown by the DataHandler. Refer to the
	 *                documentation for javax.activation.DataHandler for more
	 *                details.
	 */
	public Object getContent() throws IOException, MessagingException
	{
		if (cachedContent != null) return cachedContent;
		Object c;
		try
		{
			c = getDataHandler().getContent();
		}
		catch (FolderClosedIOException fex)
		{
			throw new FolderClosedException(fex.getFolder(), fex.getMessage());
		}
		catch (MessageRemovedIOException mex)
		{
			throw new MessageRemovedException(mex.getMessage());
		}
		if (cacheMultipart && (c instanceof Multipart || c instanceof Message)
				&& (content != null || contentStream != null))
		{
			cachedContent = c;
		}
		return c;
	}

	/**
	 * This method provides the mechanism to set this body part's content. The
	 * given DataHandler object should wrap the actual content.
	 * 
	 * @param dh
	 *            The DataHandler for the content
	 * @exception IllegalWriteException
	 *                if the underlying implementation does not support
	 *                modification
	 * @exception IllegalStateException
	 *                if this body part is obtained from a READ_ONLY folder.
	 */
	public void setDataHandler(DataHandler dh) throws MessagingException
	{
		this.dh = dh;
		cachedContent = null;
		MimeBodyPart.invalidateContentHeaders(this);
	}

	/**
	 * A convenience method for setting this body part's content.
	 * <p>
	 * The content is wrapped in a DataHandler object. Note that a
	 * DataContentHandler class for the specified type should be available to
	 * the JavaMail implementation for this to work right. That is, to do
	 * <code>setContent(foobar, "application/x-foobar")</code>, a
	 * DataContentHandler for "application/x-foobar" should be installed. Refer
	 * to the Java Activation Framework for more information.
	 * 
	 * @param o
	 *            the content object
	 * @param type
	 *            Mime type of the object
	 * @exception IllegalWriteException
	 *                if the underlying implementation does not support
	 *                modification of existing values
	 * @exception IllegalStateException
	 *                if this body part is obtained from a READ_ONLY folder.
	 */
	public void setContent(Object o, String type) throws MessagingException
	{
		if (o instanceof Multipart)
		{
			setContent((Multipart) o);
		}
		else
		{
			setDataHandler(new DataHandler(o, type));
		}
	}

	/**
	 * Convenience method that sets the given String as this part's content,
	 * with a MIME type of "text/plain". If the string contains non US-ASCII
	 * characters, it will be encoded using the platform's default charset. The
	 * charset is also used to set the "charset" parameter.
	 * <p>
	 * Note that there may be a performance penalty if <code>text</code> is
	 * large, since this method may have to scan all the characters to determine
	 * what charset to use.
	 * <p>
	 * If the charset is already known, use the <code>setText</code> method that
	 * takes the charset parameter.
	 * 
	 * @param text
	 *            the text content to set
	 * @exception MessagingException
	 *                if an error occurs
	 * @see #setText(String text, String charset)
	 */
	public void setText(String text) throws MessagingException
	{
		setText(text, null);
	}

	/**
	 * Convenience method that sets the given String as this part's content,
	 * with a MIME type of "text/plain" and the specified charset. The given
	 * Unicode string will be charset-encoded using the specified charset. The
	 * charset is also used to set the "charset" parameter.
	 * 
	 * @param text
	 *            the text content to set
	 * @param charset
	 *            the charset to use for the text
	 * @exception MessagingException
	 *                if an error occurs
	 */
	public void setText(String text, String charset) throws MessagingException
	{
		setText(this, text, charset, "plain");
	}

	/**
	 * Convenience method that sets the given String as this part's content,
	 * with a primary MIME type of "text" and the specified MIME subtype. The
	 * given Unicode string will be charset-encoded using the specified charset.
	 * The charset is also used to set the "charset" parameter.
	 * 
	 * @param text
	 *            the text content to set
	 * @param charset
	 *            the charset to use for the text
	 * @param subtype
	 *            the MIME subtype to use (e.g., "html")
	 * @exception MessagingException
	 *                if an error occurs
	 * @since JavaMail 1.4
	 */
	public void setText(String text, String charset, String subtype)
			throws MessagingException
	{
		setText(this, text, charset, subtype);
	}

	/**
	 * This method sets the body part's content to a Multipart object.
	 * 
	 * @param mp
	 *            The multipart object that is the Message's content
	 * @exception IllegalWriteException
	 *                if the underlying implementation does not support
	 *                modification of existing values.
	 * @exception IllegalStateException
	 *                if this body part is obtained from a READ_ONLY folder.
	 */
	public void setContent(Multipart mp) throws MessagingException
	{
		setDataHandler(new DataHandler(mp, mp.getContentType()));
		mp.setParent(this);
	}

	/**
	 * Use the specified file to provide the data for this part. The simple file
	 * name is used as the file name for this part and the data in the file is
	 * used as the data for this part. The encoding will be chosen appropriately
	 * for the file data.
	 * 
	 * @param file
	 *            the File object to attach
	 * @exception IOException
	 *                errors related to accessing the file
	 * @exception MessagingException
	 *                message related errors
	 * @since JavaMail 1.4
	 */
	public void attachFile(File file) throws IOException, MessagingException
	{
		FileDataSource fds = new FileDataSource(file);
		this.setDataHandler(new DataHandler(fds));
		this.setFileName(fds.getName());
	}

	/**
	 * Use the specified file to provide the data for this part. The simple file
	 * name is used as the file name for this part and the data in the file is
	 * used as the data for this part. The encoding will be chosen appropriately
	 * for the file data.
	 * 
	 * @param file
	 *            the name of the file to attach
	 * @exception IOException
	 *                errors related to accessing the file
	 * @exception MessagingException
	 *                message related errors
	 * @since JavaMail 1.4
	 */
	public void attachFile(String file) throws IOException, MessagingException
	{
		File f = new File(file);
		attachFile(f);
	}

	/**
	 * Save the contents of this part in the specified file. The content is
	 * decoded and saved, without any of the MIME headers.
	 * 
	 * @param file
	 *            the File object to write to
	 * @exception IOException
	 *                errors related to accessing the file
	 * @exception MessagingException
	 *                message related errors
	 * @since JavaMail 1.4
	 */
	public void saveFile(File file) throws IOException, MessagingException
	{
		OutputStream out = null;
		InputStream in = null;
		try
		{
			out = new BufferedOutputStream(new FileOutputStream(file));
			in = this.getInputStream();
			byte[] buf = new byte[8192];
			int len;
			while ((len = in.read(buf)) > 0)
				out.write(buf, 0, len);
		}
		finally
		{
			// close streams, but don't mask original exception, if any
			try
			{
				if (in != null) in.close();
			}
			catch (IOException ex)
			{
			}
			try
			{
				if (out != null) out.close();
			}
			catch (IOException ex)
			{
			}
		}
	}

	/**
	 * Save the contents of this part in the specified file. The content is
	 * decoded and saved, without any of the MIME headers.
	 * 
	 * @param file
	 *            the name of the file to write to
	 * @exception IOException
	 *                errors related to accessing the file
	 * @exception MessagingException
	 *                message related errors
	 * @since JavaMail 1.4
	 */
	public void saveFile(String file) throws IOException, MessagingException
	{
		File f = new File(file);
		saveFile(f);
	}

	/**
	 * Output the body part as an RFC 822 format stream.
	 * 
	 * @exception MessagingException
	 * @exception IOException
	 *                if an error occurs writing to the stream or if an error is
	 *                generated by the javax.activation layer.
	 * @see javax.activation.DataHandler#writeTo
	 */
	public void writeTo(OutputStream os) throws IOException, MessagingException
	{
		writeTo(this, os, null);
	}

	/**
	 * Get all the headers for this header_name. Note that certain headers may
	 * be encoded as per RFC 2047 if they contain non US-ASCII characters and
	 * these should be decoded.
	 * 
	 * @param name
	 *            name of header
	 * @return array of headers
	 * @see javax.mail.internet.MimeUtility
	 */
	public String[] getHeader(String name) throws MessagingException
	{
		return headers.getHeader(name);
	}

	/**
	 * Get all the headers for this header name, returned as a single String,
	 * with headers separated by the delimiter. If the delimiter is
	 * <code>null</code>, only the first header is returned.
	 * 
	 * @param name
	 *            the name of this header
	 * @param delimiter
	 *            delimiter between fields in returned string
	 * @return the value fields for all headers with this name
	 * @exception MessagingException
	 */
	public String getHeader(String name, String delimiter)
			throws MessagingException
	{
		return headers.getHeader(name, delimiter);
	}

	/**
	 * Set the value for this header_name. Replaces all existing header values
	 * with this new value. Note that RFC 822 headers must contain only US-ASCII
	 * characters, so a header that contains non US-ASCII characters must be
	 * encoded as per the rules of RFC 2047.
	 * 
	 * @param name
	 *            header name
	 * @param value
	 *            header value
	 * @see javax.mail.internet.MimeUtility
	 */
	public void setHeader(String name, String value) throws MessagingException
	{
		headers.setHeader(name, value);
	}

	/**
	 * Add this value to the existing values for this header_name. Note that RFC
	 * 822 headers must contain only US-ASCII characters, so a header that
	 * contains non US-ASCII characters must be encoded as per the rules of RFC
	 * 2047.
	 * 
	 * @param name
	 *            header name
	 * @param value
	 *            header value
	 * @see javax.mail.internet.MimeUtility
	 */
	public void addHeader(String name, String value) throws MessagingException
	{
		headers.addHeader(name, value);
	}

	/**
	 * Remove all headers with this name.
	 */
	public void removeHeader(String name) throws MessagingException
	{
		headers.removeHeader(name);
	}

	/**
	 * Return all the headers from this Message as an Enumeration of Header
	 * objects.
	 */
	public Enumeration getAllHeaders() throws MessagingException
	{
		return headers.getAllHeaders();
	}

	/**
	 * Return matching headers from this Message as an Enumeration of Header
	 * objects.
	 * <p>
	 */
	public Enumeration getMatchingHeaders(String[] names)
			throws MessagingException
	{
		return headers.getMatchingHeaders(names);
	}

	/**
	 * Return non-matching headers from this Message as an Enumeration of Header
	 * objects.
	 */
	public Enumeration getNonMatchingHeaders(String[] names)
			throws MessagingException
	{
		return headers.getNonMatchingHeaders(names);
	}

	/**
	 * Add a header line to this body part
	 */
	public void addHeaderLine(String line) throws MessagingException
	{
		headers.addHeaderLine(line);
	}

	/**
	 * Get all header lines as an Enumeration of Strings. A Header line is a raw
	 * RFC 822 header line, containing both the "name" and "value" field.
	 */
	public Enumeration getAllHeaderLines() throws MessagingException
	{
		return headers.getAllHeaderLines();
	}

	/**
	 * Get matching header lines as an Enumeration of Strings. A Header line is
	 * a raw RFC 822 header line, containing both the "name" and "value" field.
	 */
	public Enumeration getMatchingHeaderLines(String[] names)
			throws MessagingException
	{
		return headers.getMatchingHeaderLines(names);
	}

	/**
	 * Get non-matching header lines as an Enumeration of Strings. A Header line
	 * is a raw RFC 822 header line, containing both the "name" and "value"
	 * field.
	 */
	public Enumeration getNonMatchingHeaderLines(String[] names)
			throws MessagingException
	{
		return headers.getNonMatchingHeaderLines(names);
	}

	/**
	 * Examine the content of this body part and update the appropriate MIME
	 * headers. Typical headers that get set here are <code>Content-Type</code>
	 * and <code>Content-Transfer-Encoding</code>. Headers might need to be
	 * updated in two cases: <br>
	 * - A message being crafted by a mail application will certainly need to
	 * activate this method at some point to fill up its internal headers. <br>
	 * - A message read in from a Store will have obtained all its headers from
	 * the store, and so doesn't need this. However, if this message is editable
	 * and if any edits have been made to either the content or message
	 * structure, we might need to resync our headers. <br>
	 * In both cases this method is typically called by the
	 * <code>Message.saveChanges</code> method.
	 */
	protected void updateHeaders() throws MessagingException
	{
		updateHeaders(this);
		/*
		 * If we've cached a Multipart or Message object then we're now
		 * committed to using this instance of the object and we discard any
		 * stream data used to create this object.
		 */
		if (cachedContent != null)
		{
			dh = new DataHandler(cachedContent, getContentType());
			cachedContent = null;
			content = null;
			if (contentStream != null)
			{
				try
				{
					contentStream.close();
				}
				catch (IOException ioex)
				{
				} // nothing to do
			}
			contentStream = null;
		}
	}

	// ///////////////////////////////////////////////////////////
	// Package private convenience methods to share code among //
	// MimeMessage and MimeBodyPart //
	// ///////////////////////////////////////////////////////////

	static boolean isMimeType(MimePart part, String mimeType)
			throws MessagingException
	{
		// XXX - lots of room for optimization here!
		try
		{
			ContentType ct = new ContentType(part.getContentType());
			return ct.match(mimeType);
		}
		catch (ParseException ex)
		{
			return part.getContentType().equalsIgnoreCase(mimeType);
		}
	}

	static void setText(MimePart part, String text, String charset,
			String subtype) throws MessagingException
	{
		if (charset == null)
		{
			if (MimeUtility.checkAscii(text) != MimeUtility.ALL_ASCII) charset = MimeUtility
					.getDefaultMIMECharset();
			else charset = "us-ascii";
		}
		// XXX - should at least ensure that subtype is an atom
		part.setContent(text, "text/" + subtype + "; charset="
				+ MimeUtility.quote(charset, HeaderTokenizer.MIME));
	}

	static String getDisposition(MimePart part) throws MessagingException
	{
		String s = part.getHeader("Content-Disposition", null);

		if (s == null) return null;

		ContentDisposition cd = new ContentDisposition(s);
		return cd.getDisposition();
	}

	static void setDisposition(MimePart part, String disposition)
			throws MessagingException
	{
		if (disposition == null) part.removeHeader("Content-Disposition");
		else
		{
			String s = part.getHeader("Content-Disposition", null);
			if (s != null)
			{
				/*
				 * A Content-Disposition header already exists .. Override
				 * disposition, but attempt to retain existing disposition
				 * parameters
				 */
				ContentDisposition cd = new ContentDisposition(s);
				cd.setDisposition(disposition);
				disposition = cd.toString();
			}
			part.setHeader("Content-Disposition", disposition);
		}
	}

	static String getDescription(MimePart part) throws MessagingException
	{
		String rawvalue = part.getHeader("Content-Description", null);

		if (rawvalue == null) return null;

		try
		{
			return MimeUtility.decodeText(MimeUtility.unfold(rawvalue));
		}
		catch (UnsupportedEncodingException ex)
		{
			return rawvalue;
		}
	}

	static void setDescription(MimePart part, String description, String charset)
			throws MessagingException
	{
		if (description == null)
		{
			part.removeHeader("Content-Description");
			return;
		}

		try
		{
			part.setHeader("Content-Description", MimeUtility.fold(21,
					MimeUtility.encodeText(description, charset, null)));
		}
		catch (UnsupportedEncodingException uex)
		{
			throw new MessagingException("Encoding error", uex);
		}
	}

	static String getFileName(MimePart part) throws MessagingException
	{
		String filename = null;
		String s = part.getHeader("Content-Disposition", null);

		if (s != null)
		{
			// Parse the header ..
			ContentDisposition cd = new ContentDisposition(s);
			filename = cd.getParameter("filename");
		}
		if (filename == null)
		{
			// Still no filename ? Try the "name" ContentType parameter
			s = part.getHeader("Content-Type", null);
			if (s != null)
			{
				try
				{
					ContentType ct = new ContentType(s);
					filename = ct.getParameter("name");
				}
				catch (ParseException pex)
				{
				} // ignore it
			}
		}
		if (decodeFileName && filename != null)
		{
			try
			{
				filename = MimeUtility.decodeText(filename);
			}
			catch (UnsupportedEncodingException ex)
			{
				throw new MessagingException("Can't decode filename", ex);
			}
		}
		return filename;
	}

	static void setFileName(MimePart part, String name)
			throws MessagingException
	{
		if (encodeFileName && name != null)
		{
			try
			{
				name = MimeUtility.encodeText(name);
			}
			catch (UnsupportedEncodingException ex)
			{
				throw new MessagingException("Can't encode filename", ex);
			}
		}

		// Set the Content-Disposition "filename" parameter
		String s = part.getHeader("Content-Disposition", null);
		ContentDisposition cd = new ContentDisposition(
				s == null ? Part.ATTACHMENT : s);
		cd.setParameter("filename", name);
		part.setHeader("Content-Disposition", cd.toString());

		/*
		 * Also attempt to set the Content-Type "name" parameter, to satisfy
		 * ancient MUAs. XXX - This is not RFC compliant.
		 */
		if (setContentTypeFileName)
		{
			s = part.getHeader("Content-Type", null);
			if (s != null)
			{
				try
				{
					ContentType cType = new ContentType(s);
					cType.setParameter("name", name);
					part.setHeader("Content-Type", cType.toString());
				}
				catch (ParseException pex)
				{
				} // ignore it
			}
		}
	}

	static String[] getContentLanguage(MimePart part) throws MessagingException
	{
		String s = part.getHeader("Content-Language", null);

		if (s == null) return null;

		// Tokenize the header to obtain the Language-tags (skip comments)
		HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
		Vector v = new Vector();

		HeaderTokenizer.Token tk;
		int tkType;

		while (true)
		{
			tk = h.next(); // get a language-tag
			tkType = tk.getType();
			if (tkType == HeaderTokenizer.Token.EOF) break; // done
			else if (tkType == HeaderTokenizer.Token.ATOM) v.addElement(tk
					.getValue());
			else // invalid token, skip it.
			continue;
		}

		if (v.size() == 0) return null;

		String[] language = new String[v.size()];
		v.copyInto(language);
		return language;
	}

	static void setContentLanguage(MimePart part, String[] languages)
			throws MessagingException
	{
		StringBuffer sb = new StringBuffer(languages[0]);
		for (int i = 1; i < languages.length; i++)
			sb.append(',').append(languages[i]);
		part.setHeader("Content-Language", sb.toString());
	}

	static String getEncoding(MimePart part) throws MessagingException
	{
		String s = part.getHeader("Content-Transfer-Encoding", null);

		if (s == null) return null;

		s = s.trim(); // get rid of trailing spaces
		// quick check for known values to avoid unnecessary use
		// of tokenizer.
		if (s.equalsIgnoreCase("7bit") || s.equalsIgnoreCase("8bit")
				|| s.equalsIgnoreCase("quoted-printable")
				|| s.equalsIgnoreCase("binary") || s.equalsIgnoreCase("base64")) return s;

		// Tokenize the header to obtain the encoding (skip comments)
		HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);

		HeaderTokenizer.Token tk;
		int tkType;

		for (;;)
		{
			tk = h.next(); // get a token
			tkType = tk.getType();
			if (tkType == HeaderTokenizer.Token.EOF) break; // done
			else if (tkType == HeaderTokenizer.Token.ATOM) return tk.getValue();
			else // invalid token, skip it.
			continue;
		}
		return s;
	}

	static void setEncoding(MimePart part, String encoding)
			throws MessagingException
	{
		part.setHeader("Content-Transfer-Encoding", encoding);
	}

	static void updateHeaders(MimePart part) throws MessagingException
	{
		DataHandler dh = part.getDataHandler();
		if (dh == null) // Huh ?
		return;

		try
		{
			String type = dh.getContentType();
			boolean composite = false;
			boolean needCTHeader = part.getHeader("Content-Type") == null;

			ContentType cType = new ContentType(type);
			if (cType.match("multipart/*"))
			{
				// If multipart, recurse
				composite = true;
				Object o;
				if (part instanceof MimeBodyPart)
				{
					MimeBodyPart mbp = (MimeBodyPart) part;
					o = mbp.cachedContent != null ? mbp.cachedContent : dh
							.getContent();
				}
				else if (part instanceof MimeMessage)
				{
					MimeMessage msg = (MimeMessage) part;
					o = msg.cachedContent != null ? msg.cachedContent : dh
							.getContent();
				}
				else o = dh.getContent();
				if (o instanceof MimeMultipart)
				{
					((MimeMultipart) o).updateHeaders();
				}
				else if (o instanceof IMAPInputStream)
				{
					System.err.println("Ignoring inconsistent mail body on updateHeaders()");
				}
				else
				{
					throw new MessagingException("MIME part of type \"" + type
							+ "\" contains object of type "
							+ o.getClass().getName()
							+ " instead of MimeMultipart");
				}
			}
			else if (cType.match("message/rfc822"))
			{
				composite = true;
				// XXX - call MimeMessage.updateHeaders()?
			}

			// Content-Transfer-Encoding, but only if we don't
			// already have one
			if (!composite)
			{ // not allowed on composite parts
				if (part.getHeader("Content-Transfer-Encoding") == null) setEncoding(
						part, MimeUtility.getEncoding(dh));

				if (needCTHeader && setDefaultTextCharset
						&& cType.match("text/*")
						&& cType.getParameter("charset") == null)
				{
					/*
					 * Set a default charset for text parts. We really should
					 * examine the data to determine whether or not it's all
					 * ASCII, but that's too expensive so we make an assumption:
					 * If we chose 7bit encoding for this data, it's probably
					 * ASCII. (MimeUtility.getEncoding will choose 7bit only in
					 * this case, but someone might've set the
					 * Content-Transfer-Encoding header manually.)
					 */
					String charset;
					String enc = part.getEncoding();
					if (enc != null && enc.equalsIgnoreCase("7bit")) charset = "us-ascii";
					else charset = MimeUtility.getDefaultMIMECharset();
					cType.setParameter("charset", charset);
					type = cType.toString();
				}
			}

			// Now, let's update our own headers ...

			// Content-type, but only if we don't already have one
			if (needCTHeader)
			{
				/*
				 * Pull out "filename" from Content-Disposition, and use that to
				 * set the "name" parameter. This is to satisfy older MUAs
				 * (DtMail, Roam and probably a bunch of others).
				 */
				String s = part.getHeader("Content-Disposition", null);
				if (s != null)
				{
					// Parse the header ..
					ContentDisposition cd = new ContentDisposition(s);
					String filename = cd.getParameter("filename");
					if (filename != null)
					{
						cType.setParameter("name", filename);
						type = cType.toString();
					}
				}

				part.setHeader("Content-Type", type);
			}
		}
		catch (IOException ex)
		{
			throw new MessagingException("IOException updating headers", ex);
		}
	}

	static void invalidateContentHeaders(MimePart part)
			throws MessagingException
	{
		part.removeHeader("Content-Type");
		part.removeHeader("Content-Transfer-Encoding");
	}

	static void writeTo(MimePart part, OutputStream os, String[] ignoreList)
			throws IOException, MessagingException
	{

		// see if we already have a LOS
		LineOutputStream los = null;
		if (os instanceof LineOutputStream)
		{
			los = (LineOutputStream) os;
		}
		else
		{
			los = new LineOutputStream(os);
		}

		// First, write out the header
		Enumeration hdrLines = part.getNonMatchingHeaderLines(ignoreList);
		while (hdrLines.hasMoreElements())
			los.writeln((String) hdrLines.nextElement());

		// The CRLF separator between header and content
		los.writeln();

		// Finally, the content. Encode if required.
		// XXX: May need to account for ESMTP ?
		os = MimeUtility.encode(os, part.getEncoding());
		part.getDataHandler().writeTo(os);
		os.flush(); // Needed to complete encoding
	}
}
